package service

import (
	"context"
	"math/rand"

	pb "gitee.com/architect-training/substitute-driver/verification/api/veri_code"
)

const (
	DigitStr  = "0123456789"
	LetterStr = "abcdefghijklmnopqrstuvwxyz"
	MixedStr  = "0123456789abcdefghijklmnopqrstuvwxyz"
)

type VeriCodeService struct {
	pb.UnimplementedVeriCodeServer
}

func NewVeriCodeService() *VeriCodeService {
	return &VeriCodeService{}
}

func (s *VeriCodeService) GetVeriCode(ctx context.Context, req *pb.GetVeriCodeRequest) (*pb.GetVeriCodeReply, error) {
	return &pb.GetVeriCodeReply{Code: GenCode(int(req.Length), req.Type)}, nil
}

// GenCode 用于区分类型
func GenCode(l int, t pb.TYPE) string {
	switch t {
	// 默认模式和 DIGIT 模式同样
	case pb.TYPE_DEFAULT:
		fallthrough
	case pb.TYPE_DIGIT:
		return genCode(DigitStr, l, 4)
	case pb.TYPE_LETTER:
		return genCode(LetterStr, l, 5)
	case pb.TYPE_MIXED:
		return genCode(MixedStr, l, 6)
	default:
		return genCode(DigitStr, l, 4)
	}
}

// 随机数生成优化
// 0-9: 100 	4
// 0-25: 11001 	5
// 0-35: 100011 6
// 一次随机获取 0~63 位的随机位，仅仅需要其中的 4~6 位
// 计算有效二进制数位，基于 chars 的长度
// 直接写死，因为 chars 是固定的，对应的位数也是固定的
func genCode(chars string, l, idxBits int) string {
	// 形成掩码 mask
	// 使用低六位 0000 0000 0011 1111
	// 0000 0100 0000 - 1 = 0000 0011 1111
	idxMask := 1<<idxBits - 1

	// 64 位可以使用多少次
	idxMax := 64 / idxBits

	ans := make([]byte, l)

	// 生成随机数 cache 表示随机位缓存
	for i, cache, remained := 0, rand.Int63(), idxMax; i < l; {
		// 如果使用的剩余次数为 0，则重新获取
		if remained == 0 {
			cache, remained = rand.Int63(), idxMax
		}

		// 利用掩码获取有效部位的随机数位
		// 00110100 10110100 10110100 10110100 10110100 10100010 10110100 10110100
		// &
		// 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00111111
		// =
		// 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00110100
		if index := int(cache & int64(idxMask)); index < len(chars) {
			ans[i] = chars[index]
			i++
		}

		// 使用下一组随机位
		cache >>= idxBits

		// 减少一次使用次数
		remained--
	}
	return string(ans)
}

// 生成验证码核心方法
// 1.20 之后不需要指定随机数种子
//func genCode(chars string, l int) string {
//	n := len(chars)
//	arr := make([]byte, l)
//	for i := 0; i < l; i++ {
//		index := rand.Intn(n)
//		arr[i] = chars[index]
//	}
//	return string(arr)
//}
